#include "pwext.hpp"
#include <list>
#include <stdio.h>

class data;

// copy of ex03ck
class barrier 
{
public:
	barrier(int count) :
		flip_(0), flop_(0), generation_(false), 
		count_(count)
	{ }

	// May be called only by the control thread, to
	// adjust the number of workers
	int add_workers(int add)
	{
		count_ += add; // add may be negative
		return count_;
	}

	// Workers call this method to synchronize on the
	// barrier
	void sync()
	{
		pw::lockmutex lm(flop_);
		bool gen = generation_;
		flip_.signal(1);
		while(gen == generation_)
			flop_.wait();
	}

	// Control calls this method to wait for the barrier
	// to be reached by all the workers
	void wait()
	{
		flip_.wait(count_);
	}

	// Control calls this method to release the workers
	// from the barrier
	void signal()
	{
		pw::lockmutex lm(flop_);
		generation_ = !generation_;
		flop_.broadcast();
	}

protected:
	// control waits for workers
	pw::semaphore flip_; 
	// workers wait for control
	pw::pmcond flop_; 
	// alternates between the steps
	bool generation_; 
	// number of worker threads synchronized by the
	// barrier
	int count_; 
};

// [[ex03cl]]
// DEBUG descr Usage of two barriers for parallel computations.
class worker : public pw::pwthread
{
public:
	worker(data *d, pw::barrier *exchb, 
			pw::barrier *stepb) :
		data_(d), exchb_(exchb), stepb_(stepb), 
		stop_(false)
	{ }

	virtual void *execute()
	{
		while (!stop_) {
			// ... exchange the data with the neighbors ...
	// DEBUG {
			fprintf(stderr, "--- worker %p exchange data %p\n", this, data_);
	// DEBUG }
			exchb_->sync();
			if (stop_)
				break;
	// DEBUG {
			fprintf(stderr, "worker %p compute data %p\n", this, data_);
	// DEBUG }
			// ... perform the computations ...
			stepb_->sync();
		}
		return 0;
	}

	void stop()
	{
		stop_ = true;
	}

protected:
	data *data_;
	pw::barrier *exchb_, *stepb_;
	bool stop_;
};

// the function running in the control thread, process
// a list of data in parallel
void control(std::list<data *> &dlist, int steps)
{
	pw::barrier exchb(0), stepb(0);
	std::list <worker *> workers;

	// start the workers
	for (std::list<data *>::iterator it = dlist.begin(); 
			it != dlist.end(); ++it) {
		worker *w = new worker(*it, &exchb, &stepb);
		workers.push_back(w);
		exchb.add_workers(1);
		stepb.add_workers(1);
		w->start();
	}

	for (int i = 0; i < steps; i++) {
		exchb.wait();
		exchb.signal();
		stepb.wait();
		stepb.signal();
	}

	// all the calculations are done
	exchb.wait();
	for (std::list<worker *>::iterator it = 
			workers.begin(); it != workers.end(); ++it) 
		(*it)->stop();
	exchb.signal();

	for (std::list<worker *>::iterator it = 
			workers.begin(); it != workers.end(); ++it) {
		(*it)->join();
		delete (*it);
	}
}
// END

// DEBUG {
class data {
public:
	int x_;
};

int main()
{
	std::list<data *> dd;
	dd.push_back(new data);
	dd.push_back(new data);
	dd.push_back(new data);
	control(dd, 3);
	while(!dd.empty()) {
		delete dd.front();
		dd.pop_front();
	}
	return 0;
}
// DEBUG }
/* Sample output:

--- worker 0x66e0d0 exchange data 0x66e010
--- worker 0x66e290 exchange data 0x66e050
--- worker 0x66e450 exchange data 0x66e090
worker 0x66e0d0 compute data 0x66e010
worker 0x66e290 compute data 0x66e050
worker 0x66e450 compute data 0x66e090
--- worker 0x66e0d0 exchange data 0x66e010
--- worker 0x66e290 exchange data 0x66e050
--- worker 0x66e450 exchange data 0x66e090
worker 0x66e0d0 compute data 0x66e010
worker 0x66e290 compute data 0x66e050
worker 0x66e450 compute data 0x66e090
--- worker 0x66e0d0 exchange data 0x66e010
--- worker 0x66e450 exchange data 0x66e090
--- worker 0x66e290 exchange data 0x66e050
worker 0x66e0d0 compute data 0x66e010
worker 0x66e450 compute data 0x66e090
worker 0x66e290 compute data 0x66e050
--- worker 0x66e0d0 exchange data 0x66e010
--- worker 0x66e450 exchange data 0x66e090
--- worker 0x66e290 exchange data 0x66e050

*/
